﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/7/26 7:22:53 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Linq;
using Machine.DataAccess.Operation;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;

namespace Machine.DataAccess.Common.ORM
{
    abstract class DbSchemInfoBase
    {
        protected static ConcurrentDictionary<string, ConcurrentDictionary<Type, DbSchemItem>> SchemCache { get; private set; }
        protected static ConcurrentDictionary<string,ConcurrentSet<string>> TableCache { get; set; }

        static DbSchemInfoBase()
        {
            TableCache = new ConcurrentDictionary<string, ConcurrentSet<string>>();
            SchemCache = new ConcurrentDictionary<string, ConcurrentDictionary<Type, DbSchemItem>>();
        }

        //public DbSchemInfoBase() {  }

        public DbSchemInfoBase(ProviderElement providerElement)
        {
            this.ProviderElement = providerElement;
        }

        public virtual DbSchemItem GetSchem(Type type)
        {
            if (SchemCache.ContainsKey(ProviderElement.Key) 
                && SchemCache[ProviderElement.Key].ContainsKey(type) 
                && SchemCache[ProviderElement.Key][type] != null) 
                return SchemCache[ProviderElement.Key][type];

            DbSchemItem schemItem = new DbSchemItem();
            schemItem.TableName = type.GetTableName();//GetTableName(type);
            try
            {
                schemItem.PrimaryKeyName = GetPrimaryKey(schemItem.TableName);
                schemItem.Colunms = GetTableColums(schemItem.TableName);
            }
            catch (InvalidOperationException)
            {
                throw new Exception("找不到主键，请确认是否有这张表");
            }
            schemItem.IsAuto = GetPrimaryKeyType(schemItem.TableName);
            schemItem.ForeignKeys = GetForeignKey(schemItem.TableName).ToDictionary(x => x.Table);

            if (!SchemCache.ContainsKey(ProviderElement.Key)) SchemCache[ProviderElement.Key] = new ConcurrentDictionary<Type, DbSchemItem>();
            SchemCache[ProviderElement.Key][type] = schemItem;

            return schemItem;
        }

        public virtual void RemoveSchem(Type type)
        {
            if (!SchemCache.ContainsKey(ProviderElement.Key)) return;
            if (SchemCache[ProviderElement.Key].ContainsKey(type)) SchemCache[ProviderElement.Key][type] = null;
            RemoveFkCache(type.GetTableName());
        }

        protected abstract void RemoveFkCache(string type);

        public TableRelationType GetTableRelationType(Type firstType, Type secondType)
        {
            var firstProperties = DynamicAssignment.Instance.GetDictionary(firstType);
            var secondProperties = DynamicAssignment.Instance.GetDictionary(secondType);

            var firstRelation = GetTableRelationType(secondType, firstProperties);
            var secondRelation = GetTableRelationType(firstType, secondProperties);

            var relation = (TableRelationType)Enum.Parse(typeof(TableRelationType), string.Format("{0}2{1}", firstRelation, secondRelation));
            if (firstType == secondType && relation == TableRelationType.Many2Many) return TableRelationType.One2Many;
            return relation;
        }
        private SingleTableRelationType GetTableRelationType(Type compareType, PropertyInvokerDic properties)
        {
            foreach (var property in properties)
            {
                var propertyType = property.Value.PropertyInfo.PropertyType;
                if (propertyType == compareType) return SingleTableRelationType.One;
                if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(ITable<>) && propertyType.GetGenericArguments()[0] == compareType)
                    return SingleTableRelationType.Many;
                //if(property.Value.PropertyInfo.PropertyType.IsGenericType && )
            }
            return SingleTableRelationType.Zero;
        }

        //protected abstract string GetTableName(Type type);
        public abstract string GetPrimaryKey(string tableName);
        public abstract bool GetPrimaryKeyType(string tableName);
        public abstract DbForeignKeyInfo[] GetForeignKey(string tableName);
        public DbParameter CreateDbParameter(string parameterName,object value)
        {
            var parameter = DbProviderFactories.GetFactory(ProviderElement.Provider).CreateParameter();//Config.Instance.ProviderFactory.CreateParameter();
            parameter.ParameterName = parameterName;
            parameter.Value = value;

            return parameter;
        }

        public abstract string[] GetAllTables();
        public abstract bool ExtistTable(string name);
        public abstract Tuple<string, DbForeignKeyInfo[]> GetOuterFKey(Type bigTypeName, Type collectionTypeName);
        //public virtual string ProviderName { get; set; }
        public ProviderElement ProviderElement { get; private set; }

        protected abstract ConcurrentSet<string> GetTableColums(string tableName);
        protected virtual DbConnection GetConnection()
        {
            var factory = DbProviderFactories.GetFactory(this.ProviderElement.Provider);
            var connection = factory.CreateConnection();
            connection.ConnectionString = this.ProviderElement.ConnectionString;
            connection.Open();
            return connection;
        }

        //public virtual DbForeignKeyInfo[] GetAllRelationFK(Type type)
        //{
        //    var allTables = this.GetAllTables();
        //    var tableName = type.GetTableName();
        //    List<DbForeignKeyInfo> keys = new List<DbForeignKeyInfo>();
        //    foreach (var item in allTables)
        //    {
        //        var temp = this.GetForeignKey(item).ToDictionary(x => x.Table);
        //        if (temp.ContainsKey(tableName)) keys.Add(temp[tableName]);
        //    }
        //    return keys.ToArray();

        //}
    }

    class DbSchemItem
    {
        public string TableName { get; set; }
        public string PrimaryKeyName { get; set; }
        public bool IsAuto { get; set; }
        public ConcurrentSet<string> Colunms { get; set; }
        public IDictionary<string, DbForeignKeyInfo> ForeignKeys { get; set; }
    }

    class DbForeignKeyInfo
    {
        public string Name { get; set; }
        public string From { get; set; }
        public string To { get; set; }
        private string _table;
        public string Table
        {
            get { return _table; }
            set { _table = value.ToLower(); }
        }
    }
}
